package cn.sangedon.review.analysis.stastics.specific;

import cn.hutool.core.date.DateUtil;
import cn.sangedon.review.analysis.constant.StrategyEnum;
import cn.sangedon.review.analysis.service.StockStasticsService;
import cn.sangedon.review.common.constant.CacheConstant;
import cn.sangedon.review.common.constant.TypeConstant;
import cn.sangedon.review.common.domain.analysis.StockStastics;
import cn.sangedon.review.common.domain.analysis.StockStrategy;
import cn.sangedon.review.common.domain.data.StockDayInfo;
import cn.sangedon.review.common.service.data.CacheService;
import cn.sangedon.review.common.service.data.StockDayInfoService;
import cn.sangedon.review.common.service.data.TradingDateService;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

/**
 * 每日交易数据统计分析
 * 统计每只股票涨跌，成交量放量缩量等
 */
@Slf4j
@Component
public class DailyTradeDataStastics extends AbstractScheduleStastics {
    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private StockStasticsService stockStasticsService;

    @DubboReference
    private TradingDateService tradingDateService;

    @DubboReference
    private StockDayInfoService stockDayInfoService;

    @DubboReference
    private CacheService cacheService;

    @Override
    public void runJob(List<StockStrategy> strategyList) {
        strategyList.stream().filter(e -> e.getStrategyName().equals(StrategyEnum.DAILY_STASTICS.getName())).findFirst().ifPresent(e -> {
            addSchedule(e);
        });
    }

    @Override
    public void stastic() {
        log.info("开始执行每日交易数据分析");
        if (checkStasticsRefresh()) {
            log.info("交易统计数据已是最新数据，无需重复刷新");
            return;
        }

        List<StockStastics> stockStastics = stockStasticsService.listLatest();
        if (CollectionUtils.isEmpty(stockStastics)) {
            refreshStockStastics(20);
            return;
        }

        List<StockDayInfo> stockDayInfos = stockDayInfoService.getStockDayInfos(stockStastics.get(0).getTradeDate(), TypeConstant.GT);
        if (CollectionUtils.isEmpty(stockDayInfos)) {
            log.info("{}日股票日交易数据还未更新", DateUtil.today());
            return;
        }

        Map<String, List<StockDayInfo>> stockInfoMap = stockDayInfos.parallelStream()
                                                                    .collect(Collectors.groupingBy(StockDayInfo::getTradingDate));
        List<String> tradeDates = (List<String>) stockInfoMap.keySet().stream().sorted().collect(Collectors.toList());

        Map<String, StockStastics> stasticsMap = stockStastics.parallelStream().map(e -> {
            e.setId(null);
            return e;
        }).collect(Collectors.toMap(StockStastics::getStockCode, Function.identity()));

        batchStastics(tradeDates, stockInfoMap, stasticsMap);
        log.info("执行每日交易数据分析结束");
    }

    /**
     * 刷新【最近多少个交易日(参数)】交易分析数据
     *
     * @param days 根据指定的天数的交易数据来分析
     */
    public void refreshStockStastics(Integer days) {
        log.info("开始刷新最近{}个交易日的分析数据", days);
        String cacheKey = CacheConstant.LATEST_TRADING_INFO + days;
        if (!redisTemplate.opsForHash().hasKey(cacheKey, DateUtil.yesterday().toDateStr())) {
            cacheService.refreshStockDayInfo(days);
        }

        if (checkStasticsRefresh()) {
            log.info("交易统计数据已是最新数据，无需重复刷新");
            return;
        }

        Map map = redisTemplate.opsForHash().entries(cacheKey);
        List<String> tradeDates = (List<String>) map.keySet().stream().sorted().collect(Collectors.toList());
        Map<String, StockStastics> stasticsMap = stockStasticsService.listLatest().parallelStream().map(e -> {
            e.setId(null);
            e.setTradeDate(null);
            return e;
        }).collect(Collectors.toMap(StockStastics::getStockCode, Function.identity()));

        batchStastics(tradeDates, map, stasticsMap);
        log.info("最近{}个交易日的分析数据成功刷新", days);
    }

    /**
     * 批量刷新交易日统计数据
     *
     * @param tradeDates 待刷新从小到大排好序的交易日
     * @param map 按照交易日分类的各交易日数据Map
     * @param stasticsMap 按照code分类的分析数据Map
     */
    private void batchStastics(List<String> tradeDates, Map map, Map<String, StockStastics> stasticsMap) {
        for (String tradeDate : tradeDates) {
            List<StockDayInfo> stockDayInfos = (List<StockDayInfo>) map.get(tradeDate);
            stockDayInfos.forEach(stockDayInfo -> {
                stasticsMap.put(stockDayInfo.getCode(),
                                handleStockStastics(stasticsMap.getOrDefault(stockDayInfo.getCode(), new StockStastics()), stockDayInfo));
            });
            stasticsMap.values().forEach(e -> e.setId(null));
            stockStasticsService.saveOrUpdateBatch(stasticsMap.values());
        }
    }

    /**
     * 分析交易统计信息
     *
     * @param stockStastics 已有的交易统计
     * @param stockDayInfo 当前待分析的交易日数据，之前的交易统计数据一定要是当前交易日数据的前一天分析出来的
     */
    private StockStastics handleStockStastics(StockStastics stockStastics, StockDayInfo stockDayInfo) {
        String preTradingDate = tradingDateService.preTradingDay(stockDayInfo.getTradingDate(), 1).current().getTradingDate();
        if (StringUtils.isNotEmpty(stockStastics.getTradeDate()) && !stockStastics.getTradeDate().equals(preTradingDate)) {
            log.error("已有交易分析数据非今日交易数据前一天分析结果，stastics:{}, stock:{}", stockStastics, stockDayInfo);
            throw new RuntimeException("已有交易分析数据非今日交易数据前一天分析结果");
        }
        stockStastics.setTradeDate(stockDayInfo.getTradingDate());
        stockStastics.setStockCode(stockDayInfo.getCode());
        stockStastics.setStockName(stockDayInfo.getName());
        stockStastics.setCurrentPrice(stockDayInfo.getClosingPrice());
        if (stockDayInfo.getChangeRate().signum() > 0) {
            stockStastics.setRiseDay(Optional.ofNullable(stockStastics.getRiseDay()).orElse(0) + 1);
            stockStastics.setFallDay(0);
        } else if (stockDayInfo.getChangeRate().signum() < 0) {
            stockStastics.setFallDay(Optional.ofNullable(stockStastics.getFallDay()).orElse(0) + 1);
            stockStastics.setRiseDay(0);
        } else if (stockDayInfo.getChangeRate().signum() == 0 && stockDayInfo.getTradingVolume().signum() > 0) {
            if (Optional.ofNullable(stockStastics.getRiseDay()).orElse(0) != 0) {
                stockStastics.setRiseDay(stockStastics.getRiseDay() + 1);
            } else {
                stockStastics.setFallDay(Optional.ofNullable(stockStastics.getFallDay()).orElse(0) + 1);
            }
        }

        if (stockStastics.getMaxPrice() == null || stockStastics.getMaxPrice().compareTo(stockDayInfo.getPeakPrice()) < 0) {
            stockStastics.setMaxPrice(stockDayInfo.getPeakPrice());
            stockStastics.setMaxPriceDate(stockDayInfo.getTradingDate());
        }

        if (stockStastics.getMinPrice() == null || stockDayInfo.getBottomPrice().compareTo(stockStastics.getMinPrice()) < 0) {
            stockStastics.setMinPrice(stockDayInfo.getBottomPrice());
            stockStastics.setMinPriceDate(stockDayInfo.getTradingDate());
        }

        if (stockStastics.getLastDayTradeVol() != null &&
            stockDayInfo.getTradingVolume().compareTo(stockStastics.getLastDayTradeVol()) > 0) {
            stockStastics.setEnlargeVolumeDay(Optional.ofNullable(stockStastics.getEnlargeVolumeDay()).orElse(0) + 1);
            stockStastics.setReduceVolumeDay(0);
        } else {
            stockStastics.setReduceVolumeDay(Optional.ofNullable(stockStastics.getReduceVolumeDay()).orElse(0) + 1);
            stockStastics.setEnlargeVolumeDay(0);
        }
        stockStastics.setLastDayTradeVol(stockDayInfo.getTradingVolume());

        List<Double> vols = new ArrayList<>();
        if (StringUtils.isNotEmpty(stockStastics.getLatestTradeVol20())) {
            JSONArray array = JSONArray.parseArray(stockStastics.getLatestTradeVol20());
            array.forEach(e -> vols.add(Double.parseDouble(e.toString())));
        }

        vols.add(0, stockDayInfo.getTradingVolume().doubleValue());
        vols.stream().limit(20).collect(Collectors.toList());
        stockStastics.setLatestTradeVol20(JSONObject.toJSONString(vols));

        stockStastics.setAverageVolume3(vols.stream().limit(3).mapToDouble(e -> e).sum() / 3);
        stockStastics.setAverageVolume5(vols.stream().limit(5).mapToDouble(e -> e).sum() / 5);
        stockStastics.setAverageVolume10(vols.stream().limit(10).mapToDouble(e -> e).sum() / 10);
        stockStastics.setAverageVolume20(vols.stream().limit(20).mapToDouble(e -> e).sum() / 20);

        JSONArray latestClosePrice20 = new JSONArray();
        if (StringUtils.isNotEmpty(stockStastics.getLatestClosePrice20())) {
            latestClosePrice20 = JSONArray.parseArray(stockStastics.getLatestClosePrice20());
        }
        latestClosePrice20.add(0, stockDayInfo.getClosingPrice());
        stockStastics.setLatestClosePrice20(latestClosePrice20.stream().limit(20).collect(Collectors.toList()).toString());

        JSONArray latestOpenPrice20 = new JSONArray();
        if (StringUtils.isNotEmpty(stockStastics.getLatestOpenPrice20())) {
            latestOpenPrice20 = JSONArray.parseArray(stockStastics.getLatestOpenPrice20());
        }
        latestOpenPrice20.add(0, stockDayInfo.getOpeningPrice());
        stockStastics.setLatestOpenPrice20(latestOpenPrice20.stream().limit(20).collect(Collectors.toList()).toString());
        return stockStastics;
    }

    /**
     * 校验是否需要刷新统计数据
     *
     * @return true 不需要
     */
    private boolean checkStasticsRefresh() {
        String latestDate = stockDayInfoService.getLatestDate();
        String stasticsDate = stockStasticsService.getStasticsDate();
        if (StringUtils.isEmpty(stasticsDate)) {
            return false;
        }
        return DateUtil.parseDate(stasticsDate).isAfterOrEquals(DateUtil.parseDate(latestDate));
    }
}
